Notebook summary

Identifying presumed seroconverters

One approach to identifying a seropositivity cutoff in a longitudinal study is to identify children who presumably seroconvert from negative to positive based on a change in their antibody levels, and then use the distribution of their antibody levels before seroconversion to identify a seropositivity cutoff. The rationale for this two-step process that estimates a cutoff (and does not simply classify children as seroconverers versus not) is that the cutoff can then be applied to identify children who were seropositive but did not actually seroconvert during the study. This was common for antibody response to enteric pathogens in the study cohorts, where exposure ocurred frequently and very early in life.

Level of increase in antibody levels to identify seroconversion

The method relies on choosing the appropriate magnitude of change in antibody levels used identify presumed seroconverters, which is generally unknown. Most immunogenicity abnd pathogen challenge studies have used a 4-fold increase. In this study, we used an increase of +2 or more on the log\(_{10}\) scale, equal to a 100-fold increase, to identify presumed seroconverters. Although this was likely a conservative assumption, it still led to high levels of agreement with other classification approachs (generally >95%) when applied across cohorts and antibodies.

Sensitivity analysis description

Here, we conduct a sensitivity analysis that searches over a range of increases in antibody levels used to identify “presumed seroconverters”. For each increase, we then calculated the mean and SD of the distribution before the change, and used that as a seropositivity cutoff. We then compared the classification accuracy of each value against classifications based on cutoffs derived from ROC-curves or mixture models, as available.

In the present study, we used two additional methods to determine seropositivity cutoffs:

  • For Giardia (VSP-3, VSP-5), Cryptosporidium (Cp17, Cp23), and E. histolytica (LecA) antigens, known negative and positive samples were used to identify an optimal cutoff based on a receiver-operator characteristic (ROC) curve.
  • For all other pathogens, we fit a 2-component, finite Gaussian mixture model to the antibody distributions, and then used the lower component’s mean plus 3 standard deviations to define a cutoff value. This identified reasonable cutoff values for many, but not all, additional antibodies. In some cases the mixture model based approach did not identify reasonable cutoff values (i.e., near or above the upper limit assay’s dynamic range).

Given this other classification information, we compared a range of changes in antibody levels used to identify children who presumably seroconverted in terms of agreement with ROC-based and mixture model-based classification of seropositivity.

We searched over a range of changes from +0.3 to +2.5 on the log10 MFI-bg scale (this corresponds to a 10^0.3 = 2-fold to 10^2.5 = 316-fold increase in antibody levels). For each change in antibody levels (0.3 to 2.5), we identified the children who would be identified as seroconverters. From these children, we then estimated the mean plus 3 standard deviations of their antibody levels at their first measurement, when they were presumably unexposed. This is the seropositivity cutoff among the “presumed unexposed”. We then estimated agreement and Cohen’s Kappa statistics for each change and identify the magnitude of change that corresponded with the maximum level of agreement.

Sensitivity analysis results

The optimal change in MFI levels used to identify presumed seroconverters varied by cohort and antigen, where “optimal” was defined as the minimum change in MFI with maximum agreement with respect to ROC-based or mixture model-based seropositivity classifications.

For antigens with both ROC-based cutoffs and mixture model-based cutoffs, results were similar between the two comparisons. This result reflects good agreement between ROC- and mixture model-based cutoffs (detailed results in this article’s Supplementary Information File 2).

Over the range of values considered, the method led to cutoff values with classification agreement that was very high compared with both ROC- and mixture model-based classifications. In Haiti, optimal agreement exceeded 0.92 for all comparisons. In Kenya, optimal agreement exceeded 0.98 for all comparisons.

In Haiti, the change in MFI values that led to highest agreement were generally \(\geq 2\) on the log\(_{10}\) scale. In Kenya, change values were lower and generally in the range of 0.5 to 1.5. In general, across both countries, there was not a large decline in agreement at higher change values imposed to identify presumed seroconverters when developing a cutoff (an exception was Cryptosporidium Cp23 in Kenya).

Taken together, this sensitivity analysis supports the use of an increase of +2 log\(_{10}\) MFI to identify presumed seroconverters in the definition of seropositivity cutoffs for antigens considered.

Script preamble

#-----------------------------
# preamble
#-----------------------------
library(here)
here() starts at /Users/benarnold/enterics-seroepi
here()
[1] "/Users/benarnold/enterics-seroepi"
library(tidyverse)
── Attaching packages ──────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.0.0     ✔ purrr   0.2.4
✔ tibble  1.4.2     ✔ dplyr   0.7.4
✔ tidyr   0.8.0     ✔ stringr 1.3.1
✔ readr   1.1.1     ✔ forcats 0.3.0
── Conflicts ─────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(psych)

Attaching package: ‘psych’

The following objects are masked from ‘package:ggplot2’:

    %+%, alpha
library(gridExtra)

Attaching package: ‘gridExtra’

The following object is masked from ‘package:dplyr’:

    combine
# set up for parallel computing
# configure for a laptop (use only 3 cores)
library(foreach)

Attaching package: ‘foreach’

The following objects are masked from ‘package:purrr’:

    accumulate, when
library(doParallel)
Loading required package: iterators
Loading required package: parallel
registerDoParallel(cores=3)
# bright color blind palette:  https://personal.sron.nl/~pault/ 
cblack <- "#000004FF"
cblue <- "#3366AA"
cteal <- "#11AA99"
cgreen <- "#66AA55"
cchartr <- "#CCCC55"
cmagent <- "#992288"
cred <- "#EE3333"
corange <- "#EEA722"
cyellow <- "#FFEE33"
cgrey <- "#777777"

Leogane, Haiti

Load the formatted data

#-----------------------------
# load the formatted data
# created with 
# haiti-enteric-ab-data-format.Rmd ->
# haiti-enteric-ab-distributions.Rmd
#-----------------------------
dl <- readRDS(here("data","haiti_analysis2.rds"))
# list the enteric antigens and formatted labels for them
mbavars <- c("vsp3","vsp5","cp17","cp23","leca","salb","sald","etec","norogi","norogii")
mbalabs <- c("Giardia VSP-3","Giardia VSP-5","Cryptosporidium Cp17","Cryptosporidium Cp23","E. histolytica LecA","Salmonella LPS group B","Salmonella LPS group D","ETEC LT β subunit","Norovirus GI.4", "Norovirus GII.4.NO")

Sensitivity analysis over fold-change values, ROC-based reference

Giardia, Cryptosporidium, and E. histolytica antigens have ROC-based cutoff values in this study. For these antigens, search over fold-change values to determine the cutoff with best agreement with ROC-based classification.

droc <- dl %>%
  filter(!is.na(roccut)) %>%
  droplevels()
gridroc <- foreach(ab=levels(droc$antigenf),.combine=rbind) %:%
  foreach(icut=seq(0.3,2.5,by=0.01),.combine=rbind) %dopar% {
    
    # downsample to measurements among children <=1 y 
    # who increased by the increment icut in the next period    
    # these are "presumed unexposed" under this cutoff in change icut
    # estimate mean and SD among the presumed unexposed to use as a 
    # seropositivity cutoff
    dmu <- droc %>% 
      ungroup() %>%
      select(antigenf,age,logmfi,logmfidiff) %>%
      filter(antigenf==ab & age <= 1 & logmfidiff>icut) %>%
      summarize(mu=mean(logmfi),sd=sd(logmfi),ucut=mu+3*sd)
    
    # classify all observations using the derived cutoff
    di <- droc %>%  
      filter(antigenf==ab) %>%
      mutate(posi=factor(ifelse(logmfi>dmu$ucut,1,0),levels=c(0,1)))
    
    # estimate agreement
    tabi <- as.vector(table(as.factor(di$posroc),as.factor(di$posi)))
    names(tabi) <- c("roc0cuti0","roc1cuti0","roc0cuti1","roc1cuti1")
    kappai <- psych::cohen.kappa(table(di$posroc,di$posi))$kappa
    tabi <- data.frame(t(tabi))
    tabi <- tabi %>%
      mutate(Nobs=roc0cuti0+roc1cuti0+roc0cuti1+roc1cuti1,
             agreement=(roc0cuti0+roc1cuti1)/Nobs,
             kappa=kappai,
             antigenf=ab,
             abchange=icut,
             abcutoff=dmu$ucut) 
    
  }
gridroc <- gridroc %>% mutate(antigenf=factor(antigenf,levels=levels(droc$antigenf)))
# identify the optimal change based on % agreement, take minimum change
optchgroc <- gridroc %>%
  group_by(antigenf) %>%
  mutate(maxagr=max(agreement,na.rm=TRUE),
         maxchg=ifelse( abs(agreement-maxagr)<0.001,1,0)) %>%
  filter(maxchg==1) %>%
  arrange(antigenf,abchange) %>%
  filter(row_number()==1) %>%
  select(antigenf,optchg=abchange,optagr=agreement,optkap=kappa,optcut=abcutoff)
# merge in the optimal change values
gridroc <- left_join(gridroc,optchgroc,by="antigenf")  %>%
  group_by(antigenf)

Figure to summarize results

General figure scheme:

# this general figure scheme is specific to results stored in
# gridroc and gridmix objects.
plot_optchange <- function(results,classlab="XXX reference standard") {
  
  nantigens <- length(levels(results$antigenf))
  
  # Figure of cutoff by antibody change threshold
  plot_cutoff <- ggplot(data=results,aes(x=abchange)) +
  geom_line(aes(y=abcutoff),color=cmagent) +
  geom_vline(aes(xintercept=optchg),lty="dashed")+ 
  geom_hline(aes(yintercept=optcut),lty="dashed")+ 
  # labels for optimal change and corresponding cutoff based on % agreement
  geom_label(data=filter(results,row_number()==1),
            aes(x=optchg,y=0.5,label=sprintf("%1.2f",optchg)),
            position=position_nudge(x=0),
            fill="white",label.size=0) + 
  geom_label(data=filter(results,row_number()==1),
            aes(y=optcut,x=0,label=sprintf("%1.2f",optcut)),
            position=position_nudge(y=0,x=0.4),
            fill="white",label.size=0) + 
  facet_wrap(~antigenf,nrow=1,ncol=nantigens) +
  # aesthetics
  scale_y_continuous(breaks=seq(0.5,4,by=0.5))+
  scale_x_continuous(breaks=seq(0,3,by=0.5))+
  coord_cartesian(ylim=c(0.5,4),xlim=c(0,3)) +
  labs(x="log10 change in MFI used to identify presumed seroconverters",y="Estimated seropositivity cutoff (log10 MFI-bg)") +
  theme_minimal()
  
  # Figure of Kappa by antibody change threshold
  plot_kappa <- ggplot(data=results,aes(x=abchange)) +
  # geom_line(aes(y=agreement),color=cgreen) +
  geom_line(aes(y=kappa),color=cblue)+
  geom_vline(aes(xintercept=optchg),lty="dashed")+ 
  geom_hline(aes(yintercept=optkap),lty="dashed")+ 
  # labels for optimal change and corresponding kappa based on % agreement
  geom_label(data=filter(results,row_number()==1),
            aes(x=optchg,y=0,label=sprintf("%1.2f",optchg)),
            position=position_nudge(x=0),
            fill="white",label.size=0) + 
  geom_label(data=filter(results,row_number()==1),
            aes(y=optkap,x=0,label=sprintf("%1.2f",optkap)),
            position=position_nudge(y=0,x=0.4),
            fill="white",label.size=0) + 
  facet_wrap(~antigenf,nrow=1,ncol=nantigens) +
  # aesthetics
  scale_y_continuous(breaks=seq(0,1,by=0.1))+
  scale_x_continuous(breaks=seq(0,3,by=0.5))+
  coord_cartesian(ylim=c(0,1),xlim=c(0,3)) +  
  labs(x="",y=paste("Cohen's Kappa,",classlab)) +
  theme_minimal()
  
  # Figure of Agreement by antibody change threshold
  plot_agree <- ggplot(data=results,aes(x=abchange)) +
  geom_line(aes(y=agreement),color=cgreen) +
  # geom_line(aes(y=kappa),color=corange)+
  geom_vline(aes(xintercept=optchg),lty="dashed")+ 
  geom_hline(aes(yintercept=optagr),lty="dashed")+ 
  # labels for optimal change and agreement based on % agreement
  geom_label(data=filter(results,row_number()==1),
            aes(x=optchg,y=0,label=sprintf("%1.2f",optchg)),
            position=position_nudge(x=0),
            fill="white",label.size=0) + 
  geom_label(data=filter(results,row_number()==1),
            aes(y=optagr,x=0,label=sprintf("%1.2f",optagr)),
            position=position_nudge(y=0,x=0.4),
            fill="white",label.size=0) + 
  facet_wrap(~antigenf,nrow=1,ncol=nantigens) +
  # aesthetics
  scale_y_continuous(breaks=seq(0,1,by=0.1))+
  scale_x_continuous(breaks=seq(0,3,by=0.5))+
  coord_cartesian(ylim=c(0,1),xlim=c(0,3)) +
  labs(x="",y=paste("Agreement,",classlab)) +
  theme_minimal()
  
  # stack the figures into a single panel
  grid.arrange(plot_agree,plot_kappa,plot_cutoff)
}

The bottom panel plots the estimated seropositivity cutoff as a function of change in MFI levels used to identify presumed seroconverters. The mean plus 3 SDs of the distribution of antibody levels before presumed seroconversion was used to estimate the cutoff. For a given cutoff, the middle and top panels summarize classification agreement with the ROC-based seropositiity cutoff based on Cohen’s Kappa (middle) and proportion agreement (top).

Labeled dashed lines indicate the minimum magnitude of change in antibody levels that led to highest agreement, along with the corresponding seropositivity cutoff values, Cohen’s Kappa, and agreement.

plot_optchange(results=gridroc,classlab="ROC-based classification")

Sensitivity analysis over fold-change values, mixture model-based reference

Search over fold-change values to determine the cutoff with best agreement with mixture model-based classification

dmix <- dl %>%
  filter(!is.na(mixcut)) %>%
  droplevels()
gridmix <- foreach(ab=levels(dmix$antigenf),.combine=rbind) %:%
  foreach(icut=seq(0.3,2.5,by=0.01),.combine=rbind) %dopar% {
    # downsample to measurements among children <=1 y 
    # who increased by the increment icut in the next period
    # these are "presumed unexposed" under this cutoff in change icut
    # estimate mean and SD among the presumed unexposed to use as a 
    # seropositivity cutoff
    dmu <- dmix %>% 
      ungroup() %>%
      select(antigenf,age,logmfi,logmfidiff) %>%
      filter(antigenf==ab & age <=1 & logmfidiff>icut)
    
    # if there are <2 obs, cannot estimate mean + SD, so skip
    if(nrow(dmu)>=2) {
      
      dmu <- dmu %>%
        summarize(mu=mean(logmfi),sd=sd(logmfi),ucut=mu+3*sd)
    
      # classify all observations using the derived cutoff
      di <- dmix %>%  
      filter(antigenf==ab) %>%
      mutate(posi=factor(ifelse(logmfi>dmu$ucut,1,0),levels=c(0,1)),
             posmix=factor(posmix,levels=c(0,1)))
    
      tabi <- as.vector(table(di$posmix,di$posi))
      names(tabi) <- c("mix0cuti0","mix1cuti0","mix0cuti1","mix1cuti1")
      kappai <- psych::cohen.kappa(table(di$posmix,di$posi))$kappa
      tabi <- data.frame(t(tabi))
      tabi <- tabi %>%
        mutate(Nobs=mix0cuti0+mix1cuti0+mix0cuti1+mix1cuti1,
             agreement=(mix0cuti0+mix1cuti1)/Nobs,
             kappa=kappai,
             antigenf=ab,
             abchange=icut,
             abcutoff=dmu$ucut) 
      
    } else{
      tabi <- data.frame(mix0cuti0=NA,mix1cuti0=NA,mix0cuti1=NA,mix1cuti1=NA,Nobs=NA,agreement=NA,kappa=NA,antigenf=ab,abchange=icut,abcutoff=NA)
      
      }
    }  
gridmix <- gridmix %>% mutate(antigenf=factor(antigenf,levels=levels(dmix$antigenf)))
# identify the optimal change based on % agreement, take minimum change
optchgmix <- gridmix %>%
  group_by(antigenf) %>%
  mutate(maxagr=max(agreement,na.rm=TRUE),
         maxchg=ifelse( abs(agreement-maxagr)<0.001,1,0)) %>%
  filter(maxchg==1) %>%
  # mutate(agr99=ifelse(agreement>0.99 & !is.na(agreement),1,0)) %>%
  # filter(agr99==1) %>%
  arrange(antigenf,abchange) %>%
  filter(row_number()==1) %>%
  select(antigenf,optchg=abchange,optagr=agreement,optkap=kappa,optcut=abcutoff)
gridmix <- left_join(gridmix,optchgmix,by="antigenf") %>%
  group_by(antigenf)

Figure to summarize results

The bottom panel plots the estimated seropositivity cutoff as a function of change in MFI levels used to identify presumed seroconverters. The mean plus 3 SDs of the distribution of antibody levels before presumed seroconversion was used to estimate the cutoff. For a given cutoff, the middle and top panels summarize classification agreement with the mixture model-based seropositiity cutoff based on Cohen’s Kappa (middle) and proportion agreement (top).

Labeled dashed lines indicate the minimum magnitude of change in antibody levels that led to highest agreement, along with the corresponding seropositivity cutoff values, Cohen’s Kappa, and agreement.

plot_optchange(results=gridmix,classlab="Mixture model-based classification")

Asembo, Kenya

Load the formatted data

#-----------------------------
# load the formatted data
# created with 
# asembo-enteric-ab-data-format.Rmd ->
# asembo-enteric-ab-distributions.Rmd
#-----------------------------
dl <- readRDS(here("data","asembo_analysis2.rds"))
# list the enteric antigens and make formatted labels for them
mbavars <- c("vsp3","vsp5","cp17","cp23","leca","salb","sald","etec","cholera","p18","p39")
mbalabs <- c("Giardia VSP-3","Giardia VSP-5","Cryptosporidium Cp17","Cryptosporidium Cp23","E. histolytica LecA","Salmonella LPS group B","Salmonella LPS group D","ETEC LT β subunit","Cholera toxin β subunit","Campylobacter p18","Campylobacter p39")
dl <- dl %>% mutate(antigenf=factor(antigenf,levels=mbalabs))

Sensitivity analysis over fold-change values, ROC-based reference

Giardia and Cryptosporidium antigens have ROC-based cutoff values in this study. For these antigens, search over fold-change values to determine the cutoff with best agreement with ROC-based classification.

droc <- dl %>%
  filter(!is.na(roccut)) %>%
  mutate(antigenf=droplevels(antigenf))
gridroc <- foreach(ab=levels(droc$antigenf),.combine=rbind) %:%
  foreach(icut=seq(0.3,2.5,by=0.01),.combine=rbind) %dopar% {
    
    # downsample to first measurements who increased by the increment icut
    # these are "presumed unexposed" under this cutoff in change icut
    # estimate mean and SD among the presumed unexposed to use as a 
    # seropositivity cutoff
    dmu <- droc %>% 
      ungroup() %>%
      select(antigenf,time,logmfi,logmfidiff) %>%
      filter(antigenf==ab & time=="A" & logmfidiff>icut) %>%
      summarize(mu=mean(logmfi),sd=sd(logmfi),ucut=mu+3*sd)
    
    # classify all observations using the derived cutoff
    di <- droc %>%  
      filter(antigenf==ab) %>%
      mutate(posi=factor(ifelse(logmfi>dmu$ucut,1,0),levels=c(0,1)))
    
    # estimate agreement
    tabi <- as.vector(table(as.factor(di$posroc),as.factor(di$posi)))
    names(tabi) <- c("roc0cuti0","roc1cuti0","roc0cuti1","roc1cuti1")
    kappai <- psych::cohen.kappa(table(di$posroc,di$posi))$kappa
    tabi <- data.frame(t(tabi))
    tabi <- tabi %>%
      mutate(Nobs=roc0cuti0+roc1cuti0+roc0cuti1+roc1cuti1,
             agreement=(roc0cuti0+roc1cuti1)/Nobs,
             kappa=kappai,
             antigenf=ab,
             abchange=icut,
             abcutoff=dmu$ucut) 
    
  }
gridroc <- gridroc %>% mutate(antigenf=factor(antigenf,levels=levels(droc$antigenf)))
# identify the optimal change based on % agreement, take minimum change
optchgroc <- gridroc %>%
  group_by(antigenf) %>%
  mutate(maxagr=max(agreement,na.rm=TRUE),
         maxchg=ifelse( abs(agreement-maxagr)<0.001,1,0)) %>%
  filter(maxchg==1) %>%
  arrange(antigenf,abchange) %>%
  filter(row_number()==1) %>%
  select(antigenf,optchg=abchange,optagr=agreement,optkap=kappa,optcut=abcutoff)
# merge in the optimal change values
gridroc <- left_join(gridroc,optchgroc,by="antigenf")  %>%
  group_by(antigenf)

Figure to summarize results

The bottom panel plots the estimated seropositivity cutoff as a function of change in MFI levels used to identify presumed seroconverters. The mean plus 3 SDs of the distribution of antibody levels before presumed seroconversion was used to estimate the cutoff. For a given cutoff, the middle and top panels summarize classification agreement with the ROC-based seropositiity cutoff based on Cohen’s Kappa (middle) and proportion agreement (top).

Labeled dashed lines indicate the minimum magnitude of change in antibody levels that led to highest agreement, along with the corresponding seropositivity cutoff values, Cohen’s Kappa, and agreement.

plot_optchange(results=gridroc,classlab="ROC-based classification")

Sensitivity analysis over fold-change values, mixture model-based reference

Search over fold-change values to determine the cutoff with best agreement with mixture model-based classification

dmix <- dl %>%
  filter(!is.na(mixcut)) %>%
  mutate(antigenf=droplevels(antigenf))
gridmix <- foreach(ab=levels(dmix$antigenf),.combine=rbind) %:%
  foreach(icut=seq(0.3,2.5,by=0.01),.combine=rbind) %dopar% {
    # downsample to first measurements who increased by the increment icut
    # these are "presumed unexposed" under this cutoff in change icut
    # estimate mean and SD among the presumed unexposed to use as a 
    # seropositivity cutoff
    dmu <- dmix %>% 
      ungroup() %>%
      select(antigenf,time,logmfi,logmfidiff) %>%
      filter(antigenf==ab & time=="A" & logmfidiff>icut)
    
    # if there are <2 obs, cannot estimate mean + SD, so skip
    if(nrow(dmu)>=2) {
      
      dmu <- dmu %>%
        summarize(mu=mean(logmfi),sd=sd(logmfi),ucut=mu+3*sd)
    
      # classify all observations using the derived cutoff
      di <- dmix %>%  
      filter(antigenf==ab) %>%
      mutate(posi=factor(ifelse(logmfi>dmu$ucut,1,0),levels=c(0,1)),
             posmix=factor(posmix,levels=c(0,1)))
    
      tabi <- as.vector(table(di$posmix,di$posi))
      names(tabi) <- c("mix0cuti0","mix1cuti0","mix0cuti1","mix1cuti1")
      kappai <- psych::cohen.kappa(table(di$posmix,di$posi))$kappa
      tabi <- data.frame(t(tabi))
      tabi <- tabi %>%
        mutate(Nobs=mix0cuti0+mix1cuti0+mix0cuti1+mix1cuti1,
             agreement=(mix0cuti0+mix1cuti1)/Nobs,
             kappa=kappai,
             antigenf=ab,
             abchange=icut,
             abcutoff=dmu$ucut) 
      
    } else{
      tabi <- data.frame(mix0cuti0=NA,mix1cuti0=NA,mix0cuti1=NA,mix1cuti1=NA,Nobs=NA,agreement=NA,kappa=NA,antigenf=ab,abchange=icut,abcutoff=NA)
      
      }
    }  
gridmix <- gridmix %>% mutate(antigenf=factor(antigenf,levels=levels(dmix$antigenf)))
# identify the optimal change based on % agreement, take minimum change
optchgmix <- gridmix %>%
  group_by(antigenf) %>%
  mutate(maxagr=max(agreement,na.rm=TRUE),
         maxchg=ifelse( abs(agreement-maxagr)<0.001,1,0)) %>%
  filter(maxchg==1) %>%
  # mutate(agr99=ifelse(agreement>0.99 & !is.na(agreement),1,0)) %>%
  # filter(agr99==1) %>%
  arrange(antigenf,abchange) %>%
  filter(row_number()==1) %>%
  select(antigenf,optchg=abchange,optagr=agreement,optkap=kappa,optcut=abcutoff)
gridmix <- left_join(gridmix,optchgmix,by="antigenf") %>%
  group_by(antigenf)

Figure to summarize results

The bottom panel plots the estimated seropositivity cutoff as a function of change in MFI levels used to identify presumed seroconverters. The mean plus 3 SDs of the distribution of antibody levels before presumed seroconversion was used to estimate the cutoff. For a given cutoff, the middle and top panels summarize classification agreement with the mixture model-based seropositiity cutoff based on Cohen’s Kappa (middle) and proportion agreement (top).

Labeled dashed lines indicate the minimum magnitude of change in antibody levels that led to highest agreement, along with the corresponding seropositivity cutoff values, Cohen’s Kappa, and agreement.

plot_optchange(results=gridmix,classlab="Mixture model-based classification")

Session Info

sessionInfo()
R version 3.5.0 (2018-04-23)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2    doParallel_1.0.11 iterators_1.0.9   foreach_1.4.4    
 [5] gridExtra_2.3     psych_1.8.4       forcats_0.3.0     stringr_1.3.1    
 [9] dplyr_0.7.4       purrr_0.2.4       readr_1.1.1       tidyr_0.8.0      
[13] tibble_1.4.2      ggplot2_3.0.0     tidyverse_1.2.1   here_0.1         

loaded via a namespace (and not attached):
 [1] reshape2_1.4.3   haven_1.1.1      lattice_0.20-35  colorspace_1.3-2 yaml_2.1.19     
 [6] rlang_0.2.2      pillar_1.2.2     foreign_0.8-70   glue_1.2.0       withr_2.1.2     
[11] modelr_0.1.2     readxl_1.1.0     bindr_0.1.1      plyr_1.8.4       munsell_0.5.0   
[16] gtable_0.2.0     cellranger_1.1.0 rvest_0.3.2      codetools_0.2-15 knitr_1.20      
[21] broom_0.4.4      Rcpp_0.12.18     scales_1.0.0     backports_1.1.2  jsonlite_1.5    
[26] mnormt_1.5-5     hms_0.4.2        stringi_1.2.2    grid_3.5.0       rprojroot_1.3-2 
[31] cli_1.0.0        tools_3.5.0      magrittr_1.5     lazyeval_0.2.1   crayon_1.3.4    
[36] pkgconfig_2.0.2  xml2_1.2.0       lubridate_1.7.4  assertthat_0.2.0 httr_1.3.1      
[41] rstudioapi_0.7   R6_2.2.2         nlme_3.1-137     compiler_3.5.0  
LS0tCnRpdGxlOiBFbnRlcm9wYXRob2dlbiBzZXJvZXBpZGVtaW9sb2d5IGFtb25nIGNoaWxkcmVuIGluIGxvdy1yZXNvdXJjZSBzZXR0aW5ncwpzdWJ0aXRsZTogU3VwcGxlbWVudGFyeSBJbmZvcm1hdGlvbiBGaWxlIDUuIFNlbnNpdGl2aXR5IGFuYWx5c2lzIG9mIGNoYW5nZSBpbiBJZ0cgdXNlZCB0byBpZGVudGlmeSBwcmVzdW1lZCB1bmV4cG9zZWQgbWVhc3VyZW1lbnRzIGluIEhhaXRpIGFuZCBLZW55YS4Kb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IGRlZmF1bHQKICAgIGhpZ2hsaWdodDogaGFkZG9jawogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKLS0tCgojIE5vdGVib29rIHN1bW1hcnkKCiMjIyBJZGVudGlmeWluZyBwcmVzdW1lZCBzZXJvY29udmVydGVycwpPbmUgYXBwcm9hY2ggdG8gaWRlbnRpZnlpbmcgYSBzZXJvcG9zaXRpdml0eSBjdXRvZmYgaW4gYSBsb25naXR1ZGluYWwgc3R1ZHkgaXMgdG8gaWRlbnRpZnkgY2hpbGRyZW4gd2hvIHByZXN1bWFibHkgc2Vyb2NvbnZlcnQgZnJvbSBuZWdhdGl2ZSB0byBwb3NpdGl2ZSBiYXNlZCBvbiBhIGNoYW5nZSBpbiB0aGVpciBhbnRpYm9keSBsZXZlbHMsIGFuZCB0aGVuIHVzZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZWlyIGFudGlib2R5IGxldmVscyBiZWZvcmUgc2Vyb2NvbnZlcnNpb24gdG8gaWRlbnRpZnkgYSBzZXJvcG9zaXRpdml0eSBjdXRvZmYuICBUaGUgcmF0aW9uYWxlIGZvciB0aGlzIHR3by1zdGVwIHByb2Nlc3MgdGhhdCBlc3RpbWF0ZXMgYSBjdXRvZmYgKGFuZCBkb2VzIG5vdCBzaW1wbHkgY2xhc3NpZnkgY2hpbGRyZW4gYXMgc2Vyb2NvbnZlcmVycyB2ZXJzdXMgbm90KSBpcyB0aGF0IHRoZSBjdXRvZmYgY2FuIHRoZW4gYmUgYXBwbGllZCB0byBpZGVudGlmeSBjaGlsZHJlbiB3aG8gd2VyZSBzZXJvcG9zaXRpdmUgYnV0IGRpZCBub3QgYWN0dWFsbHkgc2Vyb2NvbnZlcnQgZHVyaW5nIHRoZSBzdHVkeS4gVGhpcyB3YXMgY29tbW9uIGZvciBhbnRpYm9keSByZXNwb25zZSB0byBlbnRlcmljIHBhdGhvZ2VucyBpbiB0aGUgc3R1ZHkgY29ob3J0cywgd2hlcmUgZXhwb3N1cmUgb2N1cnJlZCBmcmVxdWVudGx5IGFuZCB2ZXJ5IGVhcmx5IGluIGxpZmUuCgojIyMgTGV2ZWwgb2YgaW5jcmVhc2UgaW4gYW50aWJvZHkgbGV2ZWxzIHRvIGlkZW50aWZ5IHNlcm9jb252ZXJzaW9uClRoZSBtZXRob2QgcmVsaWVzIG9uIGNob29zaW5nIHRoZSBhcHByb3ByaWF0ZSBtYWduaXR1ZGUgb2YgY2hhbmdlIGluIGFudGlib2R5IGxldmVscyB1c2VkIGlkZW50aWZ5IHByZXN1bWVkIHNlcm9jb252ZXJ0ZXJzLCB3aGljaCBpcyBnZW5lcmFsbHkgdW5rbm93bi4gIE1vc3QgaW1tdW5vZ2VuaWNpdHkgYWJuZCBwYXRob2dlbiBjaGFsbGVuZ2Ugc3R1ZGllcyBoYXZlIHVzZWQgYSA0LWZvbGQgaW5jcmVhc2UuIEluIHRoaXMgc3R1ZHksIHdlIHVzZWQgYW4gaW5jcmVhc2Ugb2YgKzIgb3IgbW9yZSBvbiB0aGUgbG9nJF97MTB9JCBzY2FsZSwgZXF1YWwgdG8gYSAxMDAtZm9sZCBpbmNyZWFzZSwgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMuIEFsdGhvdWdoIHRoaXMgd2FzIGxpa2VseSBhIGNvbnNlcnZhdGl2ZSBhc3N1bXB0aW9uLCBpdCBzdGlsbCBsZWQgdG8gaGlnaCBsZXZlbHMgb2YgYWdyZWVtZW50IHdpdGggb3RoZXIgY2xhc3NpZmljYXRpb24gYXBwcm9hY2hzIChnZW5lcmFsbHkgPjk1JSkgd2hlbiBhcHBsaWVkIGFjcm9zcyBjb2hvcnRzIGFuZCBhbnRpYm9kaWVzLgoKIyMjIFNlbnNpdGl2aXR5IGFuYWx5c2lzIGRlc2NyaXB0aW9uIAoKSGVyZSwgd2UgY29uZHVjdCBhIHNlbnNpdGl2aXR5IGFuYWx5c2lzIHRoYXQgc2VhcmNoZXMgb3ZlciBhIHJhbmdlIG9mIGluY3JlYXNlcyBpbiBhbnRpYm9keSBsZXZlbHMgdXNlZCB0byBpZGVudGlmeSAicHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMiLiBGb3IgZWFjaCBpbmNyZWFzZSwgd2UgdGhlbiBjYWxjdWxhdGVkIHRoZSBtZWFuIGFuZCBTRCBvZiB0aGUgZGlzdHJpYnV0aW9uIGJlZm9yZSB0aGUgY2hhbmdlLCBhbmQgdXNlZCB0aGF0IGFzIGEgc2Vyb3Bvc2l0aXZpdHkgY3V0b2ZmLiBXZSB0aGVuIGNvbXBhcmVkIHRoZSBjbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBvZiBlYWNoIHZhbHVlIGFnYWluc3QgY2xhc3NpZmljYXRpb25zIGJhc2VkIG9uIGN1dG9mZnMgZGVyaXZlZCBmcm9tIFJPQy1jdXJ2ZXMgb3IgbWl4dHVyZSBtb2RlbHMsIGFzIGF2YWlsYWJsZS4KCkluIHRoZSBwcmVzZW50IHN0dWR5LCB3ZSB1c2VkIHR3byBhZGRpdGlvbmFsIG1ldGhvZHMgdG8gZGV0ZXJtaW5lIHNlcm9wb3NpdGl2aXR5IGN1dG9mZnM6CgogICAqIEZvciBfR2lhcmRpYV8gKFZTUC0zLCBWU1AtNSksIF9DcnlwdG9zcG9yaWRpdW1fIChDcDE3LCBDcDIzKSwgYW5kIF9FLiBoaXN0b2x5dGljYV8gKExlY0EpIGFudGlnZW5zLCBrbm93biBuZWdhdGl2ZSBhbmQgcG9zaXRpdmUgc2FtcGxlcyB3ZXJlIHVzZWQgdG8gaWRlbnRpZnkgYW4gb3B0aW1hbCBjdXRvZmYgYmFzZWQgb24gYSByZWNlaXZlci1vcGVyYXRvciBjaGFyYWN0ZXJpc3RpYyAoUk9DKSBjdXJ2ZS4gIAogICAqIEZvciBhbGwgb3RoZXIgcGF0aG9nZW5zLCB3ZSBmaXQgYSAyLWNvbXBvbmVudCwgZmluaXRlIEdhdXNzaWFuIG1peHR1cmUgbW9kZWwgdG8gdGhlIGFudGlib2R5IGRpc3RyaWJ1dGlvbnMsIGFuZCB0aGVuIHVzZWQgdGhlIGxvd2VyIGNvbXBvbmVudCdzIG1lYW4gcGx1cyAzIHN0YW5kYXJkIGRldmlhdGlvbnMgdG8gZGVmaW5lIGEgY3V0b2ZmIHZhbHVlLiBUaGlzIGlkZW50aWZpZWQgcmVhc29uYWJsZSBjdXRvZmYgdmFsdWVzIGZvciBtYW55LCBidXQgbm90IGFsbCwgYWRkaXRpb25hbCBhbnRpYm9kaWVzLiBJbiBzb21lIGNhc2VzIHRoZSBtaXh0dXJlIG1vZGVsIGJhc2VkIGFwcHJvYWNoIGRpZCBub3QgaWRlbnRpZnkgcmVhc29uYWJsZSBjdXRvZmYgdmFsdWVzIChpLmUuLCBuZWFyIG9yIGFib3ZlIHRoZSB1cHBlciBsaW1pdCBhc3NheSdzIGR5bmFtaWMgcmFuZ2UpLgoKR2l2ZW4gdGhpcyBvdGhlciBjbGFzc2lmaWNhdGlvbiBpbmZvcm1hdGlvbiwgd2UgY29tcGFyZWQgYSByYW5nZSBvZiBjaGFuZ2VzIGluIGFudGlib2R5IGxldmVscyB1c2VkIHRvIGlkZW50aWZ5IGNoaWxkcmVuIHdobyBwcmVzdW1hYmx5IHNlcm9jb252ZXJ0ZWQgaW4gdGVybXMgb2YgYWdyZWVtZW50IHdpdGggUk9DLWJhc2VkIGFuZCBtaXh0dXJlIG1vZGVsLWJhc2VkIGNsYXNzaWZpY2F0aW9uIG9mIHNlcm9wb3NpdGl2aXR5LiAgCgpXZSBzZWFyY2hlZCBvdmVyIGEgcmFuZ2Ugb2YgY2hhbmdlcyBmcm9tICswLjMgdG8gKzIuNSBvbiB0aGUgbG9nMTAgTUZJLWJnIHNjYWxlICh0aGlzIGNvcnJlc3BvbmRzIHRvIGEgMTBeMC4zID0gMi1mb2xkIHRvIDEwXjIuNSA9IDMxNi1mb2xkIGluY3JlYXNlIGluIGFudGlib2R5IGxldmVscykuIEZvciBlYWNoIGNoYW5nZSBpbiBhbnRpYm9keSBsZXZlbHMgKDAuMyB0byAyLjUpLCB3ZSBpZGVudGlmaWVkIHRoZSBjaGlsZHJlbiB3aG8gd291bGQgYmUgaWRlbnRpZmllZCBhcyBzZXJvY29udmVydGVycy4gIEZyb20gdGhlc2UgY2hpbGRyZW4sIHdlIHRoZW4gZXN0aW1hdGVkIHRoZSBtZWFuIHBsdXMgMyBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHRoZWlyIGFudGlib2R5IGxldmVscyBhdCB0aGVpciBmaXJzdCBtZWFzdXJlbWVudCwgd2hlbiB0aGV5IHdlcmUgcHJlc3VtYWJseSB1bmV4cG9zZWQuIFRoaXMgaXMgdGhlIHNlcm9wb3NpdGl2aXR5IGN1dG9mZiBhbW9uZyB0aGUgInByZXN1bWVkIHVuZXhwb3NlZCIuICBXZSB0aGVuIGVzdGltYXRlZCBhZ3JlZW1lbnQgYW5kIENvaGVuJ3MgS2FwcGEgc3RhdGlzdGljcyBmb3IgZWFjaCBjaGFuZ2UgYW5kIGlkZW50aWZ5IHRoZSBtYWduaXR1ZGUgb2YgY2hhbmdlIHRoYXQgY29ycmVzcG9uZGVkIHdpdGggdGhlIG1heGltdW0gbGV2ZWwgb2YgYWdyZWVtZW50LgoKIyMjIFNlbnNpdGl2aXR5IGFuYWx5c2lzIHJlc3VsdHMKClRoZSBvcHRpbWFsIGNoYW5nZSBpbiBNRkkgbGV2ZWxzIHVzZWQgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMgdmFyaWVkIGJ5IGNvaG9ydCBhbmQgYW50aWdlbiwgd2hlcmUgIm9wdGltYWwiIHdhcyBkZWZpbmVkIGFzIHRoZSBtaW5pbXVtIGNoYW5nZSBpbiBNRkkgd2l0aCBtYXhpbXVtIGFncmVlbWVudCB3aXRoIHJlc3BlY3QgdG8gUk9DLWJhc2VkIG9yIG1peHR1cmUgbW9kZWwtYmFzZWQgc2Vyb3Bvc2l0aXZpdHkgY2xhc3NpZmljYXRpb25zLiAKCkZvciBhbnRpZ2VucyB3aXRoIGJvdGggUk9DLWJhc2VkIGN1dG9mZnMgYW5kIG1peHR1cmUgbW9kZWwtYmFzZWQgY3V0b2ZmcywgcmVzdWx0cyB3ZXJlIHNpbWlsYXIgYmV0d2VlbiB0aGUgdHdvIGNvbXBhcmlzb25zLiBUaGlzIHJlc3VsdCByZWZsZWN0cyBnb29kIGFncmVlbWVudCBiZXR3ZWVuIFJPQy0gYW5kIG1peHR1cmUgbW9kZWwtYmFzZWQgY3V0b2ZmcyAoZGV0YWlsZWQgcmVzdWx0cyBpbiB0aGlzIGFydGljbGUncyBTdXBwbGVtZW50YXJ5IEluZm9ybWF0aW9uIEZpbGUgMikuIAoKT3ZlciB0aGUgcmFuZ2Ugb2YgdmFsdWVzIGNvbnNpZGVyZWQsIHRoZSBtZXRob2QgbGVkIHRvIGN1dG9mZiB2YWx1ZXMgd2l0aCBjbGFzc2lmaWNhdGlvbiBhZ3JlZW1lbnQgdGhhdCB3YXMgdmVyeSBoaWdoIGNvbXBhcmVkIHdpdGggYm90aCBST0MtIGFuZCBtaXh0dXJlIG1vZGVsLWJhc2VkIGNsYXNzaWZpY2F0aW9ucy4gSW4gSGFpdGksIG9wdGltYWwgYWdyZWVtZW50IGV4Y2VlZGVkIDAuOTIgZm9yIGFsbCBjb21wYXJpc29ucy4gSW4gS2VueWEsIG9wdGltYWwgYWdyZWVtZW50IGV4Y2VlZGVkIDAuOTggZm9yIGFsbCBjb21wYXJpc29ucy4KCkluIEhhaXRpLCB0aGUgY2hhbmdlIGluIE1GSSB2YWx1ZXMgdGhhdCBsZWQgdG8gaGlnaGVzdCBhZ3JlZW1lbnQgd2VyZSBnZW5lcmFsbHkgJFxnZXEgMiQgb24gdGhlIGxvZyRfezEwfSQgc2NhbGUuIEluIEtlbnlhLCBjaGFuZ2UgdmFsdWVzIHdlcmUgbG93ZXIgYW5kIGdlbmVyYWxseSBpbiB0aGUgcmFuZ2Ugb2YgMC41IHRvIDEuNS4gIEluIGdlbmVyYWwsIGFjcm9zcyBib3RoIGNvdW50cmllcywgdGhlcmUgd2FzIG5vdCBhIGxhcmdlIGRlY2xpbmUgaW4gYWdyZWVtZW50IGF0IGhpZ2hlciBjaGFuZ2UgdmFsdWVzIGltcG9zZWQgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMgd2hlbiBkZXZlbG9waW5nIGEgY3V0b2ZmIChhbiBleGNlcHRpb24gd2FzIF9DcnlwdG9zcG9yaWRpdW1fIENwMjMgaW4gS2VueWEpLiAKClRha2VuIHRvZ2V0aGVyLCB0aGlzIHNlbnNpdGl2aXR5IGFuYWx5c2lzIHN1cHBvcnRzIHRoZSB1c2Ugb2YgYW4gaW5jcmVhc2Ugb2YgKzIgbG9nJF97MTB9JCBNRkkgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMgaW4gdGhlIGRlZmluaXRpb24gb2Ygc2Vyb3Bvc2l0aXZpdHkgY3V0b2ZmcyBmb3IgYW50aWdlbnMgY29uc2lkZXJlZC4gCgoKIyBTY3JpcHQgcHJlYW1ibGUKYGBge3IgcHJlYW1ibGV9CiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHByZWFtYmxlCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpsaWJyYXJ5KGhlcmUpCmhlcmUoKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocHN5Y2gpCmxpYnJhcnkoZ3JpZEV4dHJhKQoKIyBzZXQgdXAgZm9yIHBhcmFsbGVsIGNvbXB1dGluZwojIGNvbmZpZ3VyZSBmb3IgYSBsYXB0b3AgKHVzZSBvbmx5IDMgY29yZXMpCmxpYnJhcnkoZm9yZWFjaCkKbGlicmFyeShkb1BhcmFsbGVsKQpyZWdpc3RlckRvUGFyYWxsZWwoY29yZXM9MykKCiMgYnJpZ2h0IGNvbG9yIGJsaW5kIHBhbGV0dGU6ICBodHRwczovL3BlcnNvbmFsLnNyb24ubmwvfnBhdWx0LyAKY2JsYWNrIDwtICIjMDAwMDA0RkYiCmNibHVlIDwtICIjMzM2NkFBIgpjdGVhbCA8LSAiIzExQUE5OSIKY2dyZWVuIDwtICIjNjZBQTU1IgpjY2hhcnRyIDwtICIjQ0NDQzU1IgpjbWFnZW50IDwtICIjOTkyMjg4IgpjcmVkIDwtICIjRUUzMzMzIgpjb3JhbmdlIDwtICIjRUVBNzIyIgpjeWVsbG93IDwtICIjRkZFRTMzIgpjZ3JleSA8LSAiIzc3Nzc3NyIKCmBgYAoKCiMgTGVvZ2FuZSwgSGFpdGkKCiMjIExvYWQgdGhlIGZvcm1hdHRlZCBkYXRhCgpgYGB7ciBoYWl0aSBkYXRhIGZvcm1hdH0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgbG9hZCB0aGUgZm9ybWF0dGVkIGRhdGEKIyBjcmVhdGVkIHdpdGggCiMgaGFpdGktZW50ZXJpYy1hYi1kYXRhLWZvcm1hdC5SbWQgLT4KIyBoYWl0aS1lbnRlcmljLWFiLWRpc3RyaWJ1dGlvbnMuUm1kCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpkbCA8LSByZWFkUkRTKGhlcmUoImRhdGEiLCJoYWl0aV9hbmFseXNpczIucmRzIikpCgojIGxpc3QgdGhlIGVudGVyaWMgYW50aWdlbnMgYW5kIGZvcm1hdHRlZCBsYWJlbHMgZm9yIHRoZW0KbWJhdmFycyA8LSBjKCJ2c3AzIiwidnNwNSIsImNwMTciLCJjcDIzIiwibGVjYSIsInNhbGIiLCJzYWxkIiwiZXRlYyIsIm5vcm9naSIsIm5vcm9naWkiKQoKbWJhbGFicyA8LSBjKCJHaWFyZGlhIFZTUC0zIiwiR2lhcmRpYSBWU1AtNSIsIkNyeXB0b3Nwb3JpZGl1bSBDcDE3IiwiQ3J5cHRvc3BvcmlkaXVtIENwMjMiLCJFLiBoaXN0b2x5dGljYSBMZWNBIiwiU2FsbW9uZWxsYSBMUFMgZ3JvdXAgQiIsIlNhbG1vbmVsbGEgTFBTIGdyb3VwIEQiLCJFVEVDIExUIM6yIHN1YnVuaXQiLCJOb3JvdmlydXMgR0kuNCIsICJOb3JvdmlydXMgR0lJLjQuTk8iKQoKYGBgCgojIyBTZW5zaXRpdml0eSBhbmFseXNpcyBvdmVyIGZvbGQtY2hhbmdlIHZhbHVlcywgUk9DLWJhc2VkIHJlZmVyZW5jZQoKX0dpYXJkaWFfLCBfQ3J5cHRvc3BvcmlkaXVtXywgYW5kIF9FLiBoaXN0b2x5dGljYV8gYW50aWdlbnMgaGF2ZSBST0MtYmFzZWQgY3V0b2ZmIHZhbHVlcyBpbiB0aGlzIHN0dWR5LiBGb3IgdGhlc2UgYW50aWdlbnMsIHNlYXJjaCBvdmVyIGZvbGQtY2hhbmdlIHZhbHVlcyB0byBkZXRlcm1pbmUgdGhlIGN1dG9mZiB3aXRoIGJlc3QgYWdyZWVtZW50IHdpdGggUk9DLWJhc2VkIGNsYXNzaWZpY2F0aW9uLgoKCmBgYHtyIGhhaXRpIHJvYyBjdXQsIHdhcm5pbmc9RkFMU0V9CmRyb2MgPC0gZGwgJT4lCiAgZmlsdGVyKCFpcy5uYShyb2NjdXQpKSAlPiUKICBkcm9wbGV2ZWxzKCkKCmdyaWRyb2MgPC0gZm9yZWFjaChhYj1sZXZlbHMoZHJvYyRhbnRpZ2VuZiksLmNvbWJpbmU9cmJpbmQpICU6JQogIGZvcmVhY2goaWN1dD1zZXEoMC4zLDIuNSxieT0wLjAxKSwuY29tYmluZT1yYmluZCkgJWRvcGFyJSB7CiAgICAKICAgICMgZG93bnNhbXBsZSB0byBtZWFzdXJlbWVudHMgYW1vbmcgY2hpbGRyZW4gPD0xIHkgCiAgICAjIHdobyBpbmNyZWFzZWQgYnkgdGhlIGluY3JlbWVudCBpY3V0IGluIHRoZSBuZXh0IHBlcmlvZCAgICAKICAgICMgdGhlc2UgYXJlICJwcmVzdW1lZCB1bmV4cG9zZWQiIHVuZGVyIHRoaXMgY3V0b2ZmIGluIGNoYW5nZSBpY3V0CiAgICAjIGVzdGltYXRlIG1lYW4gYW5kIFNEIGFtb25nIHRoZSBwcmVzdW1lZCB1bmV4cG9zZWQgdG8gdXNlIGFzIGEgCiAgICAjIHNlcm9wb3NpdGl2aXR5IGN1dG9mZgogICAgZG11IDwtIGRyb2MgJT4lIAogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIHNlbGVjdChhbnRpZ2VuZixhZ2UsbG9nbWZpLGxvZ21maWRpZmYpICU+JQogICAgICBmaWx0ZXIoYW50aWdlbmY9PWFiICYgYWdlIDw9IDEgJiBsb2dtZmlkaWZmPmljdXQpICU+JQogICAgICBzdW1tYXJpemUobXU9bWVhbihsb2dtZmkpLHNkPXNkKGxvZ21maSksdWN1dD1tdSszKnNkKQogICAgCiAgICAjIGNsYXNzaWZ5IGFsbCBvYnNlcnZhdGlvbnMgdXNpbmcgdGhlIGRlcml2ZWQgY3V0b2ZmCiAgICBkaSA8LSBkcm9jICU+JSAgCiAgICAgIGZpbHRlcihhbnRpZ2VuZj09YWIpICU+JQogICAgICBtdXRhdGUocG9zaT1mYWN0b3IoaWZlbHNlKGxvZ21maT5kbXUkdWN1dCwxLDApLGxldmVscz1jKDAsMSkpKQogICAgCiAgICAjIGVzdGltYXRlIGFncmVlbWVudAogICAgdGFiaSA8LSBhcy52ZWN0b3IodGFibGUoYXMuZmFjdG9yKGRpJHBvc3JvYyksYXMuZmFjdG9yKGRpJHBvc2kpKSkKICAgIG5hbWVzKHRhYmkpIDwtIGMoInJvYzBjdXRpMCIsInJvYzFjdXRpMCIsInJvYzBjdXRpMSIsInJvYzFjdXRpMSIpCiAgICBrYXBwYWkgPC0gcHN5Y2g6OmNvaGVuLmthcHBhKHRhYmxlKGRpJHBvc3JvYyxkaSRwb3NpKSkka2FwcGEKICAgIHRhYmkgPC0gZGF0YS5mcmFtZSh0KHRhYmkpKQogICAgdGFiaSA8LSB0YWJpICU+JQogICAgICBtdXRhdGUoTm9icz1yb2MwY3V0aTArcm9jMWN1dGkwK3JvYzBjdXRpMStyb2MxY3V0aTEsCiAgICAgICAgICAgICBhZ3JlZW1lbnQ9KHJvYzBjdXRpMCtyb2MxY3V0aTEpL05vYnMsCiAgICAgICAgICAgICBrYXBwYT1rYXBwYWksCiAgICAgICAgICAgICBhbnRpZ2VuZj1hYiwKICAgICAgICAgICAgIGFiY2hhbmdlPWljdXQsCiAgICAgICAgICAgICBhYmN1dG9mZj1kbXUkdWN1dCkgCiAgICAKICB9Cgpncmlkcm9jIDwtIGdyaWRyb2MgJT4lIG11dGF0ZShhbnRpZ2VuZj1mYWN0b3IoYW50aWdlbmYsbGV2ZWxzPWxldmVscyhkcm9jJGFudGlnZW5mKSkpCgojIGlkZW50aWZ5IHRoZSBvcHRpbWFsIGNoYW5nZSBiYXNlZCBvbiAlIGFncmVlbWVudCwgdGFrZSBtaW5pbXVtIGNoYW5nZQpvcHRjaGdyb2MgPC0gZ3JpZHJvYyAlPiUKICBncm91cF9ieShhbnRpZ2VuZikgJT4lCiAgbXV0YXRlKG1heGFncj1tYXgoYWdyZWVtZW50LG5hLnJtPVRSVUUpLAogICAgICAgICBtYXhjaGc9aWZlbHNlKCBhYnMoYWdyZWVtZW50LW1heGFncik8MC4wMDEsMSwwKSkgJT4lCiAgZmlsdGVyKG1heGNoZz09MSkgJT4lCiAgYXJyYW5nZShhbnRpZ2VuZixhYmNoYW5nZSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKT09MSkgJT4lCiAgc2VsZWN0KGFudGlnZW5mLG9wdGNoZz1hYmNoYW5nZSxvcHRhZ3I9YWdyZWVtZW50LG9wdGthcD1rYXBwYSxvcHRjdXQ9YWJjdXRvZmYpCgojIG1lcmdlIGluIHRoZSBvcHRpbWFsIGNoYW5nZSB2YWx1ZXMKZ3JpZHJvYyA8LSBsZWZ0X2pvaW4oZ3JpZHJvYyxvcHRjaGdyb2MsYnk9ImFudGlnZW5mIikgICU+JQogIGdyb3VwX2J5KGFudGlnZW5mKQoKCmBgYAoKCiMjIyBGaWd1cmUgdG8gc3VtbWFyaXplIHJlc3VsdHMKCkdlbmVyYWwgZmlndXJlIHNjaGVtZToKYGBge3IgYWdyZWVtZW50IGZpZ3VyZSBzY2hlbWV9CiMgdGhpcyBnZW5lcmFsIGZpZ3VyZSBzY2hlbWUgaXMgc3BlY2lmaWMgdG8gcmVzdWx0cyBzdG9yZWQgaW4KIyBncmlkcm9jIGFuZCBncmlkbWl4IG9iamVjdHMuCgpwbG90X29wdGNoYW5nZSA8LSBmdW5jdGlvbihyZXN1bHRzLGNsYXNzbGFiPSJYWFggcmVmZXJlbmNlIHN0YW5kYXJkIikgewogIAogIG5hbnRpZ2VucyA8LSBsZW5ndGgobGV2ZWxzKHJlc3VsdHMkYW50aWdlbmYpKQogIAogICMgRmlndXJlIG9mIGN1dG9mZiBieSBhbnRpYm9keSBjaGFuZ2UgdGhyZXNob2xkCiAgcGxvdF9jdXRvZmYgPC0gZ2dwbG90KGRhdGE9cmVzdWx0cyxhZXMoeD1hYmNoYW5nZSkpICsKICBnZW9tX2xpbmUoYWVzKHk9YWJjdXRvZmYpLGNvbG9yPWNtYWdlbnQpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PW9wdGNoZyksbHR5PSJkYXNoZWQiKSsgCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdD1vcHRjdXQpLGx0eT0iZGFzaGVkIikrIAogICMgbGFiZWxzIGZvciBvcHRpbWFsIGNoYW5nZSBhbmQgY29ycmVzcG9uZGluZyBjdXRvZmYgYmFzZWQgb24gJSBhZ3JlZW1lbnQKICBnZW9tX2xhYmVsKGRhdGE9ZmlsdGVyKHJlc3VsdHMscm93X251bWJlcigpPT0xKSwKICAgICAgICAgICAgYWVzKHg9b3B0Y2hnLHk9MC41LGxhYmVsPXNwcmludGYoIiUxLjJmIixvcHRjaGcpKSwKICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fbnVkZ2UoeD0wKSwKICAgICAgICAgICAgZmlsbD0id2hpdGUiLGxhYmVsLnNpemU9MCkgKyAKICBnZW9tX2xhYmVsKGRhdGE9ZmlsdGVyKHJlc3VsdHMscm93X251bWJlcigpPT0xKSwKICAgICAgICAgICAgYWVzKHk9b3B0Y3V0LHg9MCxsYWJlbD1zcHJpbnRmKCIlMS4yZiIsb3B0Y3V0KSksCiAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX251ZGdlKHk9MCx4PTAuNCksCiAgICAgICAgICAgIGZpbGw9IndoaXRlIixsYWJlbC5zaXplPTApICsgCiAgZmFjZXRfd3JhcCh+YW50aWdlbmYsbnJvdz0xLG5jb2w9bmFudGlnZW5zKSArCiAgIyBhZXN0aGV0aWNzCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMC41LDQsYnk9MC41KSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwzLGJ5PTAuNSkpKwogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMC41LDQpLHhsaW09YygwLDMpKSArCiAgbGFicyh4PSJsb2cxMCBjaGFuZ2UgaW4gTUZJIHVzZWQgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMiLHk9IkVzdGltYXRlZCBzZXJvcG9zaXRpdml0eSBjdXRvZmYgKGxvZzEwIE1GSS1iZykiKSArCiAgdGhlbWVfbWluaW1hbCgpCiAgCiAgIyBGaWd1cmUgb2YgS2FwcGEgYnkgYW50aWJvZHkgY2hhbmdlIHRocmVzaG9sZAogIHBsb3Rfa2FwcGEgPC0gZ2dwbG90KGRhdGE9cmVzdWx0cyxhZXMoeD1hYmNoYW5nZSkpICsKICAjIGdlb21fbGluZShhZXMoeT1hZ3JlZW1lbnQpLGNvbG9yPWNncmVlbikgKwogIGdlb21fbGluZShhZXMoeT1rYXBwYSksY29sb3I9Y2JsdWUpKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9b3B0Y2hnKSxsdHk9ImRhc2hlZCIpKyAKICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0PW9wdGthcCksbHR5PSJkYXNoZWQiKSsgCiAgIyBsYWJlbHMgZm9yIG9wdGltYWwgY2hhbmdlIGFuZCBjb3JyZXNwb25kaW5nIGthcHBhIGJhc2VkIG9uICUgYWdyZWVtZW50CiAgZ2VvbV9sYWJlbChkYXRhPWZpbHRlcihyZXN1bHRzLHJvd19udW1iZXIoKT09MSksCiAgICAgICAgICAgIGFlcyh4PW9wdGNoZyx5PTAsbGFiZWw9c3ByaW50ZigiJTEuMmYiLG9wdGNoZykpLAogICAgICAgICAgICBwb3NpdGlvbj1wb3NpdGlvbl9udWRnZSh4PTApLAogICAgICAgICAgICBmaWxsPSJ3aGl0ZSIsbGFiZWwuc2l6ZT0wKSArIAogIGdlb21fbGFiZWwoZGF0YT1maWx0ZXIocmVzdWx0cyxyb3dfbnVtYmVyKCk9PTEpLAogICAgICAgICAgICBhZXMoeT1vcHRrYXAseD0wLGxhYmVsPXNwcmludGYoIiUxLjJmIixvcHRrYXApKSwKICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fbnVkZ2UoeT0wLHg9MC40KSwKICAgICAgICAgICAgZmlsbD0id2hpdGUiLGxhYmVsLnNpemU9MCkgKyAKICBmYWNldF93cmFwKH5hbnRpZ2VuZixucm93PTEsbmNvbD1uYW50aWdlbnMpICsKICAjIGFlc3RoZXRpY3MKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgwLDEsYnk9MC4xKSkrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwzLGJ5PTAuNSkpKwogIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoMCwxKSx4bGltPWMoMCwzKSkgKyAgCiAgbGFicyh4PSIiLHk9cGFzdGUoIkNvaGVuJ3MgS2FwcGEsIixjbGFzc2xhYikpICsKICB0aGVtZV9taW5pbWFsKCkKICAKICAjIEZpZ3VyZSBvZiBBZ3JlZW1lbnQgYnkgYW50aWJvZHkgY2hhbmdlIHRocmVzaG9sZAogIHBsb3RfYWdyZWUgPC0gZ2dwbG90KGRhdGE9cmVzdWx0cyxhZXMoeD1hYmNoYW5nZSkpICsKICBnZW9tX2xpbmUoYWVzKHk9YWdyZWVtZW50KSxjb2xvcj1jZ3JlZW4pICsKICAjIGdlb21fbGluZShhZXMoeT1rYXBwYSksY29sb3I9Y29yYW5nZSkrCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1vcHRjaGcpLGx0eT0iZGFzaGVkIikrIAogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9b3B0YWdyKSxsdHk9ImRhc2hlZCIpKyAKICAjIGxhYmVscyBmb3Igb3B0aW1hbCBjaGFuZ2UgYW5kIGFncmVlbWVudCBiYXNlZCBvbiAlIGFncmVlbWVudAogIGdlb21fbGFiZWwoZGF0YT1maWx0ZXIocmVzdWx0cyxyb3dfbnVtYmVyKCk9PTEpLAogICAgICAgICAgICBhZXMoeD1vcHRjaGcseT0wLGxhYmVsPXNwcmludGYoIiUxLjJmIixvcHRjaGcpKSwKICAgICAgICAgICAgcG9zaXRpb249cG9zaXRpb25fbnVkZ2UoeD0wKSwKICAgICAgICAgICAgZmlsbD0id2hpdGUiLGxhYmVsLnNpemU9MCkgKyAKICBnZW9tX2xhYmVsKGRhdGE9ZmlsdGVyKHJlc3VsdHMscm93X251bWJlcigpPT0xKSwKICAgICAgICAgICAgYWVzKHk9b3B0YWdyLHg9MCxsYWJlbD1zcHJpbnRmKCIlMS4yZiIsb3B0YWdyKSksCiAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX251ZGdlKHk9MCx4PTAuNCksCiAgICAgICAgICAgIGZpbGw9IndoaXRlIixsYWJlbC5zaXplPTApICsgCiAgZmFjZXRfd3JhcCh+YW50aWdlbmYsbnJvdz0xLG5jb2w9bmFudGlnZW5zKSArCiAgIyBhZXN0aGV0aWNzCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoMCwxLGJ5PTAuMSkpKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDAsMyxieT0wLjUpKSsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsMSkseGxpbT1jKDAsMykpICsKICBsYWJzKHg9IiIseT1wYXN0ZSgiQWdyZWVtZW50LCIsY2xhc3NsYWIpKSArCiAgdGhlbWVfbWluaW1hbCgpCiAgCiAgIyBzdGFjayB0aGUgZmlndXJlcyBpbnRvIGEgc2luZ2xlIHBhbmVsCiAgZ3JpZC5hcnJhbmdlKHBsb3RfYWdyZWUscGxvdF9rYXBwYSxwbG90X2N1dG9mZikKCn0KCmBgYAoKVGhlIGJvdHRvbSBwYW5lbCBwbG90cyB0aGUgZXN0aW1hdGVkIHNlcm9wb3NpdGl2aXR5IGN1dG9mZiBhcyBhIGZ1bmN0aW9uIG9mIGNoYW5nZSBpbiBNRkkgbGV2ZWxzIHVzZWQgdG8gaWRlbnRpZnkgcHJlc3VtZWQgc2Vyb2NvbnZlcnRlcnMuIFRoZSBtZWFuIHBsdXMgMyBTRHMgb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhbnRpYm9keSBsZXZlbHMgYmVmb3JlIHByZXN1bWVkIHNlcm9jb252ZXJzaW9uIHdhcyB1c2VkIHRvIGVzdGltYXRlIHRoZSBjdXRvZmYuIEZvciBhIGdpdmVuIGN1dG9mZiwgdGhlIG1pZGRsZSBhbmQgdG9wIHBhbmVscyBzdW1tYXJpemUgY2xhc3NpZmljYXRpb24gYWdyZWVtZW50IHdpdGggdGhlIFJPQy1iYXNlZCBzZXJvcG9zaXRpaXR5IGN1dG9mZiBiYXNlZCBvbiBDb2hlbidzIEthcHBhIChtaWRkbGUpIGFuZCBwcm9wb3J0aW9uIGFncmVlbWVudCAodG9wKS4KCkxhYmVsZWQgZGFzaGVkIGxpbmVzIGluZGljYXRlIHRoZSBtaW5pbXVtIG1hZ25pdHVkZSBvZiBjaGFuZ2UgaW4gYW50aWJvZHkgbGV2ZWxzIHRoYXQgbGVkIHRvIGhpZ2hlc3QgYWdyZWVtZW50LCBhbG9uZyB3aXRoIHRoZSBjb3JyZXNwb25kaW5nIHNlcm9wb3NpdGl2aXR5IGN1dG9mZiB2YWx1ZXMsIENvaGVuJ3MgS2FwcGEsIGFuZCBhZ3JlZW1lbnQuCgpgYGB7ciBoYWl0aSBncmlkIHJvYyBhZ3JlZW1lbnQgZmlnLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpwbG90X29wdGNoYW5nZShyZXN1bHRzPWdyaWRyb2MsY2xhc3NsYWI9IlJPQy1iYXNlZCBjbGFzc2lmaWNhdGlvbiIpCmBgYAoKCgojIyBTZW5zaXRpdml0eSBhbmFseXNpcyBvdmVyIGZvbGQtY2hhbmdlIHZhbHVlcywgbWl4dHVyZSBtb2RlbC1iYXNlZCByZWZlcmVuY2UKClNlYXJjaCBvdmVyIGZvbGQtY2hhbmdlIHZhbHVlcyB0byBkZXRlcm1pbmUgdGhlIGN1dG9mZiB3aXRoIGJlc3QgYWdyZWVtZW50IHdpdGggbWl4dHVyZSBtb2RlbC1iYXNlZCBjbGFzc2lmaWNhdGlvbgoKYGBge3IgaGFpdGkgbWl4IGN1dCwgd2FybmluZz1GQUxTRX0KZG1peCA8LSBkbCAlPiUKICBmaWx0ZXIoIWlzLm5hKG1peGN1dCkpICU+JQogIGRyb3BsZXZlbHMoKQoKZ3JpZG1peCA8LSBmb3JlYWNoKGFiPWxldmVscyhkbWl4JGFudGlnZW5mKSwuY29tYmluZT1yYmluZCkgJTolCiAgZm9yZWFjaChpY3V0PXNlcSgwLjMsMi41LGJ5PTAuMDEpLC5jb21iaW5lPXJiaW5kKSAlZG9wYXIlIHsKCiAgICAjIGRvd25zYW1wbGUgdG8gbWVhc3VyZW1lbnRzIGFtb25nIGNoaWxkcmVuIDw9MSB5IAogICAgIyB3aG8gaW5jcmVhc2VkIGJ5IHRoZSBpbmNyZW1lbnQgaWN1dCBpbiB0aGUgbmV4dCBwZXJpb2QKICAgICMgdGhlc2UgYXJlICJwcmVzdW1lZCB1bmV4cG9zZWQiIHVuZGVyIHRoaXMgY3V0b2ZmIGluIGNoYW5nZSBpY3V0CiAgICAjIGVzdGltYXRlIG1lYW4gYW5kIFNEIGFtb25nIHRoZSBwcmVzdW1lZCB1bmV4cG9zZWQgdG8gdXNlIGFzIGEgCiAgICAjIHNlcm9wb3NpdGl2aXR5IGN1dG9mZgogICAgZG11IDwtIGRtaXggJT4lIAogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIHNlbGVjdChhbnRpZ2VuZixhZ2UsbG9nbWZpLGxvZ21maWRpZmYpICU+JQogICAgICBmaWx0ZXIoYW50aWdlbmY9PWFiICYgYWdlIDw9MSAmIGxvZ21maWRpZmY+aWN1dCkKICAgIAogICAgIyBpZiB0aGVyZSBhcmUgPDIgb2JzLCBjYW5ub3QgZXN0aW1hdGUgbWVhbiArIFNELCBzbyBza2lwCiAgICBpZihucm93KGRtdSk+PTIpIHsKICAgICAgCiAgICAgIGRtdSA8LSBkbXUgJT4lCiAgICAgICAgc3VtbWFyaXplKG11PW1lYW4obG9nbWZpKSxzZD1zZChsb2dtZmkpLHVjdXQ9bXUrMypzZCkKICAgIAogICAgICAjIGNsYXNzaWZ5IGFsbCBvYnNlcnZhdGlvbnMgdXNpbmcgdGhlIGRlcml2ZWQgY3V0b2ZmCiAgICAgIGRpIDwtIGRtaXggJT4lICAKICAgICAgZmlsdGVyKGFudGlnZW5mPT1hYikgJT4lCiAgICAgIG11dGF0ZShwb3NpPWZhY3RvcihpZmVsc2UobG9nbWZpPmRtdSR1Y3V0LDEsMCksbGV2ZWxzPWMoMCwxKSksCiAgICAgICAgICAgICBwb3NtaXg9ZmFjdG9yKHBvc21peCxsZXZlbHM9YygwLDEpKSkKICAgIAogICAgICB0YWJpIDwtIGFzLnZlY3Rvcih0YWJsZShkaSRwb3NtaXgsZGkkcG9zaSkpCiAgICAgIG5hbWVzKHRhYmkpIDwtIGMoIm1peDBjdXRpMCIsIm1peDFjdXRpMCIsIm1peDBjdXRpMSIsIm1peDFjdXRpMSIpCiAgICAgIGthcHBhaSA8LSBwc3ljaDo6Y29oZW4ua2FwcGEodGFibGUoZGkkcG9zbWl4LGRpJHBvc2kpKSRrYXBwYQogICAgICB0YWJpIDwtIGRhdGEuZnJhbWUodCh0YWJpKSkKICAgICAgdGFiaSA8LSB0YWJpICU+JQogICAgICAgIG11dGF0ZShOb2JzPW1peDBjdXRpMCttaXgxY3V0aTArbWl4MGN1dGkxK21peDFjdXRpMSwKICAgICAgICAgICAgIGFncmVlbWVudD0obWl4MGN1dGkwK21peDFjdXRpMSkvTm9icywKICAgICAgICAgICAgIGthcHBhPWthcHBhaSwKICAgICAgICAgICAgIGFudGlnZW5mPWFiLAogICAgICAgICAgICAgYWJjaGFuZ2U9aWN1dCwKICAgICAgICAgICAgIGFiY3V0b2ZmPWRtdSR1Y3V0KSAKICAgICAgCiAgICB9IGVsc2V7CiAgICAgIHRhYmkgPC0gZGF0YS5mcmFtZShtaXgwY3V0aTA9TkEsbWl4MWN1dGkwPU5BLG1peDBjdXRpMT1OQSxtaXgxY3V0aTE9TkEsTm9icz1OQSxhZ3JlZW1lbnQ9TkEsa2FwcGE9TkEsYW50aWdlbmY9YWIsYWJjaGFuZ2U9aWN1dCxhYmN1dG9mZj1OQSkKICAgICAgCiAgICAgIH0KCiAgICB9ICAKCmdyaWRtaXggPC0gZ3JpZG1peCAlPiUgbXV0YXRlKGFudGlnZW5mPWZhY3RvcihhbnRpZ2VuZixsZXZlbHM9bGV2ZWxzKGRtaXgkYW50aWdlbmYpKSkKCiMgaWRlbnRpZnkgdGhlIG9wdGltYWwgY2hhbmdlIGJhc2VkIG9uICUgYWdyZWVtZW50LCB0YWtlIG1pbmltdW0gY2hhbmdlCm9wdGNoZ21peCA8LSBncmlkbWl4ICU+JQogIGdyb3VwX2J5KGFudGlnZW5mKSAlPiUKICBtdXRhdGUobWF4YWdyPW1heChhZ3JlZW1lbnQsbmEucm09VFJVRSksCiAgICAgICAgIG1heGNoZz1pZmVsc2UoIGFicyhhZ3JlZW1lbnQtbWF4YWdyKTwwLjAwMSwxLDApKSAlPiUKICBmaWx0ZXIobWF4Y2hnPT0xKSAlPiUKICAjIG11dGF0ZShhZ3I5OT1pZmVsc2UoYWdyZWVtZW50PjAuOTkgJiAhaXMubmEoYWdyZWVtZW50KSwxLDApKSAlPiUKICAjIGZpbHRlcihhZ3I5OT09MSkgJT4lCiAgYXJyYW5nZShhbnRpZ2VuZixhYmNoYW5nZSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKT09MSkgJT4lCiAgc2VsZWN0KGFudGlnZW5mLG9wdGNoZz1hYmNoYW5nZSxvcHRhZ3I9YWdyZWVtZW50LG9wdGthcD1rYXBwYSxvcHRjdXQ9YWJjdXRvZmYpCgpncmlkbWl4IDwtIGxlZnRfam9pbihncmlkbWl4LG9wdGNoZ21peCxieT0iYW50aWdlbmYiKSAlPiUKICBncm91cF9ieShhbnRpZ2VuZikKCgpgYGAKCiMjIyBGaWd1cmUgdG8gc3VtbWFyaXplIHJlc3VsdHMKClRoZSBib3R0b20gcGFuZWwgcGxvdHMgdGhlIGVzdGltYXRlZCBzZXJvcG9zaXRpdml0eSBjdXRvZmYgYXMgYSBmdW5jdGlvbiBvZiBjaGFuZ2UgaW4gTUZJIGxldmVscyB1c2VkIHRvIGlkZW50aWZ5IHByZXN1bWVkIHNlcm9jb252ZXJ0ZXJzLiBUaGUgbWVhbiBwbHVzIDMgU0RzIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgYW50aWJvZHkgbGV2ZWxzIGJlZm9yZSBwcmVzdW1lZCBzZXJvY29udmVyc2lvbiB3YXMgdXNlZCB0byBlc3RpbWF0ZSB0aGUgY3V0b2ZmLiBGb3IgYSBnaXZlbiBjdXRvZmYsIHRoZSBtaWRkbGUgYW5kIHRvcCBwYW5lbHMgc3VtbWFyaXplIGNsYXNzaWZpY2F0aW9uIGFncmVlbWVudCB3aXRoIHRoZSBtaXh0dXJlIG1vZGVsLWJhc2VkIHNlcm9wb3NpdGlpdHkgY3V0b2ZmIGJhc2VkIG9uIENvaGVuJ3MgS2FwcGEgKG1pZGRsZSkgYW5kIHByb3BvcnRpb24gYWdyZWVtZW50ICh0b3ApLgoKTGFiZWxlZCBkYXNoZWQgbGluZXMgaW5kaWNhdGUgdGhlIG1pbmltdW0gbWFnbml0dWRlIG9mIGNoYW5nZSBpbiBhbnRpYm9keSBsZXZlbHMgdGhhdCBsZWQgdG8gaGlnaGVzdCBhZ3JlZW1lbnQsIGFsb25nIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgc2Vyb3Bvc2l0aXZpdHkgY3V0b2ZmIHZhbHVlcywgQ29oZW4ncyBLYXBwYSwgYW5kIGFncmVlbWVudC4KCgpgYGB7ciBoYWl0aSBncmlkIG1peCBhZ3JlZW1lbnQgZmlnLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD05LCB3YXJuaW5nPUZBTFNFfQpwbG90X29wdGNoYW5nZShyZXN1bHRzPWdyaWRtaXgsY2xhc3NsYWI9Ik1peHR1cmUgbW9kZWwtYmFzZWQgY2xhc3NpZmljYXRpb24iKQoKYGBgCgoKCgojIEFzZW1ibywgS2VueWEKCiMjIExvYWQgdGhlIGZvcm1hdHRlZCBkYXRhCgpgYGB7ciBkYXRhIGZvcm1hdH0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgbG9hZCB0aGUgZm9ybWF0dGVkIGRhdGEKIyBjcmVhdGVkIHdpdGggCiMgYXNlbWJvLWVudGVyaWMtYWItZGF0YS1mb3JtYXQuUm1kIC0+CiMgYXNlbWJvLWVudGVyaWMtYWItZGlzdHJpYnV0aW9ucy5SbWQKIy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmRsIDwtIHJlYWRSRFMoaGVyZSgiZGF0YSIsImFzZW1ib19hbmFseXNpczIucmRzIikpCgojIGxpc3QgdGhlIGVudGVyaWMgYW50aWdlbnMgYW5kIG1ha2UgZm9ybWF0dGVkIGxhYmVscyBmb3IgdGhlbQptYmF2YXJzIDwtIGMoInZzcDMiLCJ2c3A1IiwiY3AxNyIsImNwMjMiLCJsZWNhIiwic2FsYiIsInNhbGQiLCJldGVjIiwiY2hvbGVyYSIsInAxOCIsInAzOSIpCgptYmFsYWJzIDwtIGMoIkdpYXJkaWEgVlNQLTMiLCJHaWFyZGlhIFZTUC01IiwiQ3J5cHRvc3BvcmlkaXVtIENwMTciLCJDcnlwdG9zcG9yaWRpdW0gQ3AyMyIsIkUuIGhpc3RvbHl0aWNhIExlY0EiLCJTYWxtb25lbGxhIExQUyBncm91cCBCIiwiU2FsbW9uZWxsYSBMUFMgZ3JvdXAgRCIsIkVURUMgTFQgzrIgc3VidW5pdCIsIkNob2xlcmEgdG94aW4gzrIgc3VidW5pdCIsIkNhbXB5bG9iYWN0ZXIgcDE4IiwiQ2FtcHlsb2JhY3RlciBwMzkiKQoKZGwgPC0gZGwgJT4lIG11dGF0ZShhbnRpZ2VuZj1mYWN0b3IoYW50aWdlbmYsbGV2ZWxzPW1iYWxhYnMpKQoKYGBgCgojIyBTZW5zaXRpdml0eSBhbmFseXNpcyBvdmVyIGZvbGQtY2hhbmdlIHZhbHVlcywgUk9DLWJhc2VkIHJlZmVyZW5jZQoKX0dpYXJkaWFfIGFuZCBfQ3J5cHRvc3BvcmlkaXVtXyBhbnRpZ2VucyBoYXZlIFJPQy1iYXNlZCBjdXRvZmYgdmFsdWVzIGluIHRoaXMgc3R1ZHkuIEZvciB0aGVzZSBhbnRpZ2Vucywgc2VhcmNoIG92ZXIgZm9sZC1jaGFuZ2UgdmFsdWVzIHRvIGRldGVybWluZSB0aGUgY3V0b2ZmIHdpdGggYmVzdCBhZ3JlZW1lbnQgd2l0aCBST0MtYmFzZWQgY2xhc3NpZmljYXRpb24uCgoKYGBge3Igcm9jIGN1dCwgd2FybmluZz1GQUxTRX0KZHJvYyA8LSBkbCAlPiUKICBmaWx0ZXIoIWlzLm5hKHJvY2N1dCkpICU+JQogIG11dGF0ZShhbnRpZ2VuZj1kcm9wbGV2ZWxzKGFudGlnZW5mKSkKCmdyaWRyb2MgPC0gZm9yZWFjaChhYj1sZXZlbHMoZHJvYyRhbnRpZ2VuZiksLmNvbWJpbmU9cmJpbmQpICU6JQogIGZvcmVhY2goaWN1dD1zZXEoMC4zLDIuNSxieT0wLjAxKSwuY29tYmluZT1yYmluZCkgJWRvcGFyJSB7CiAgICAKICAgICMgZG93bnNhbXBsZSB0byBmaXJzdCBtZWFzdXJlbWVudHMgd2hvIGluY3JlYXNlZCBieSB0aGUgaW5jcmVtZW50IGljdXQKICAgICMgdGhlc2UgYXJlICJwcmVzdW1lZCB1bmV4cG9zZWQiIHVuZGVyIHRoaXMgY3V0b2ZmIGluIGNoYW5nZSBpY3V0CiAgICAjIGVzdGltYXRlIG1lYW4gYW5kIFNEIGFtb25nIHRoZSBwcmVzdW1lZCB1bmV4cG9zZWQgdG8gdXNlIGFzIGEgCiAgICAjIHNlcm9wb3NpdGl2aXR5IGN1dG9mZgogICAgZG11IDwtIGRyb2MgJT4lIAogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIHNlbGVjdChhbnRpZ2VuZix0aW1lLGxvZ21maSxsb2dtZmlkaWZmKSAlPiUKICAgICAgZmlsdGVyKGFudGlnZW5mPT1hYiAmIHRpbWU9PSJBIiAmIGxvZ21maWRpZmY+aWN1dCkgJT4lCiAgICAgIHN1bW1hcml6ZShtdT1tZWFuKGxvZ21maSksc2Q9c2QobG9nbWZpKSx1Y3V0PW11KzMqc2QpCiAgICAKICAgICMgY2xhc3NpZnkgYWxsIG9ic2VydmF0aW9ucyB1c2luZyB0aGUgZGVyaXZlZCBjdXRvZmYKICAgIGRpIDwtIGRyb2MgJT4lICAKICAgICAgZmlsdGVyKGFudGlnZW5mPT1hYikgJT4lCiAgICAgIG11dGF0ZShwb3NpPWZhY3RvcihpZmVsc2UobG9nbWZpPmRtdSR1Y3V0LDEsMCksbGV2ZWxzPWMoMCwxKSkpCiAgICAKICAgICMgZXN0aW1hdGUgYWdyZWVtZW50CiAgICB0YWJpIDwtIGFzLnZlY3Rvcih0YWJsZShhcy5mYWN0b3IoZGkkcG9zcm9jKSxhcy5mYWN0b3IoZGkkcG9zaSkpKQogICAgbmFtZXModGFiaSkgPC0gYygicm9jMGN1dGkwIiwicm9jMWN1dGkwIiwicm9jMGN1dGkxIiwicm9jMWN1dGkxIikKICAgIGthcHBhaSA8LSBwc3ljaDo6Y29oZW4ua2FwcGEodGFibGUoZGkkcG9zcm9jLGRpJHBvc2kpKSRrYXBwYQogICAgdGFiaSA8LSBkYXRhLmZyYW1lKHQodGFiaSkpCiAgICB0YWJpIDwtIHRhYmkgJT4lCiAgICAgIG11dGF0ZShOb2JzPXJvYzBjdXRpMCtyb2MxY3V0aTArcm9jMGN1dGkxK3JvYzFjdXRpMSwKICAgICAgICAgICAgIGFncmVlbWVudD0ocm9jMGN1dGkwK3JvYzFjdXRpMSkvTm9icywKICAgICAgICAgICAgIGthcHBhPWthcHBhaSwKICAgICAgICAgICAgIGFudGlnZW5mPWFiLAogICAgICAgICAgICAgYWJjaGFuZ2U9aWN1dCwKICAgICAgICAgICAgIGFiY3V0b2ZmPWRtdSR1Y3V0KSAKICAgIAogIH0KCmdyaWRyb2MgPC0gZ3JpZHJvYyAlPiUgbXV0YXRlKGFudGlnZW5mPWZhY3RvcihhbnRpZ2VuZixsZXZlbHM9bGV2ZWxzKGRyb2MkYW50aWdlbmYpKSkKCiMgaWRlbnRpZnkgdGhlIG9wdGltYWwgY2hhbmdlIGJhc2VkIG9uICUgYWdyZWVtZW50LCB0YWtlIG1pbmltdW0gY2hhbmdlCm9wdGNoZ3JvYyA8LSBncmlkcm9jICU+JQogIGdyb3VwX2J5KGFudGlnZW5mKSAlPiUKICBtdXRhdGUobWF4YWdyPW1heChhZ3JlZW1lbnQsbmEucm09VFJVRSksCiAgICAgICAgIG1heGNoZz1pZmVsc2UoIGFicyhhZ3JlZW1lbnQtbWF4YWdyKTwwLjAwMSwxLDApKSAlPiUKICBmaWx0ZXIobWF4Y2hnPT0xKSAlPiUKICBhcnJhbmdlKGFudGlnZW5mLGFiY2hhbmdlKSAlPiUKICBmaWx0ZXIocm93X251bWJlcigpPT0xKSAlPiUKICBzZWxlY3QoYW50aWdlbmYsb3B0Y2hnPWFiY2hhbmdlLG9wdGFncj1hZ3JlZW1lbnQsb3B0a2FwPWthcHBhLG9wdGN1dD1hYmN1dG9mZikKCiMgbWVyZ2UgaW4gdGhlIG9wdGltYWwgY2hhbmdlIHZhbHVlcwpncmlkcm9jIDwtIGxlZnRfam9pbihncmlkcm9jLG9wdGNoZ3JvYyxieT0iYW50aWdlbmYiKSAgJT4lCiAgZ3JvdXBfYnkoYW50aWdlbmYpCmBgYAoKCiMjIyBGaWd1cmUgdG8gc3VtbWFyaXplIHJlc3VsdHMKClRoZSBib3R0b20gcGFuZWwgcGxvdHMgdGhlIGVzdGltYXRlZCBzZXJvcG9zaXRpdml0eSBjdXRvZmYgYXMgYSBmdW5jdGlvbiBvZiBjaGFuZ2UgaW4gTUZJIGxldmVscyB1c2VkIHRvIGlkZW50aWZ5IHByZXN1bWVkIHNlcm9jb252ZXJ0ZXJzLiBUaGUgbWVhbiBwbHVzIDMgU0RzIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgYW50aWJvZHkgbGV2ZWxzIGJlZm9yZSBwcmVzdW1lZCBzZXJvY29udmVyc2lvbiB3YXMgdXNlZCB0byBlc3RpbWF0ZSB0aGUgY3V0b2ZmLiBGb3IgYSBnaXZlbiBjdXRvZmYsIHRoZSBtaWRkbGUgYW5kIHRvcCBwYW5lbHMgc3VtbWFyaXplIGNsYXNzaWZpY2F0aW9uIGFncmVlbWVudCB3aXRoIHRoZSBST0MtYmFzZWQgc2Vyb3Bvc2l0aWl0eSBjdXRvZmYgYmFzZWQgb24gQ29oZW4ncyBLYXBwYSAobWlkZGxlKSBhbmQgcHJvcG9ydGlvbiBhZ3JlZW1lbnQgKHRvcCkuCgpMYWJlbGVkIGRhc2hlZCBsaW5lcyBpbmRpY2F0ZSB0aGUgbWluaW11bSBtYWduaXR1ZGUgb2YgY2hhbmdlIGluIGFudGlib2R5IGxldmVscyB0aGF0IGxlZCB0byBoaWdoZXN0IGFncmVlbWVudCwgYWxvbmcgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBzZXJvcG9zaXRpdml0eSBjdXRvZmYgdmFsdWVzLCBDb2hlbidzIEthcHBhLCBhbmQgYWdyZWVtZW50LgoKCmBgYHtyIGdyaWQgcm9jIGFncmVlbWVudCBmaWcsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9CnBsb3Rfb3B0Y2hhbmdlKHJlc3VsdHM9Z3JpZHJvYyxjbGFzc2xhYj0iUk9DLWJhc2VkIGNsYXNzaWZpY2F0aW9uIikKYGBgCgoKIyMgU2Vuc2l0aXZpdHkgYW5hbHlzaXMgb3ZlciBmb2xkLWNoYW5nZSB2YWx1ZXMsIG1peHR1cmUgbW9kZWwtYmFzZWQgcmVmZXJlbmNlCgpTZWFyY2ggb3ZlciBmb2xkLWNoYW5nZSB2YWx1ZXMgdG8gZGV0ZXJtaW5lIHRoZSBjdXRvZmYgd2l0aCBiZXN0IGFncmVlbWVudCB3aXRoIG1peHR1cmUgbW9kZWwtYmFzZWQgY2xhc3NpZmljYXRpb24KCmBgYHtyIG1peCBjdXQsIHdhcm5pbmc9RkFMU0V9CmRtaXggPC0gZGwgJT4lCiAgZmlsdGVyKCFpcy5uYShtaXhjdXQpKSAlPiUKICBtdXRhdGUoYW50aWdlbmY9ZHJvcGxldmVscyhhbnRpZ2VuZikpCgpncmlkbWl4IDwtIGZvcmVhY2goYWI9bGV2ZWxzKGRtaXgkYW50aWdlbmYpLC5jb21iaW5lPXJiaW5kKSAlOiUKICBmb3JlYWNoKGljdXQ9c2VxKDAuMywyLjUsYnk9MC4wMSksLmNvbWJpbmU9cmJpbmQpICVkb3BhciUgewoKICAgICMgZG93bnNhbXBsZSB0byBmaXJzdCBtZWFzdXJlbWVudHMgd2hvIGluY3JlYXNlZCBieSB0aGUgaW5jcmVtZW50IGljdXQKICAgICMgdGhlc2UgYXJlICJwcmVzdW1lZCB1bmV4cG9zZWQiIHVuZGVyIHRoaXMgY3V0b2ZmIGluIGNoYW5nZSBpY3V0CiAgICAjIGVzdGltYXRlIG1lYW4gYW5kIFNEIGFtb25nIHRoZSBwcmVzdW1lZCB1bmV4cG9zZWQgdG8gdXNlIGFzIGEgCiAgICAjIHNlcm9wb3NpdGl2aXR5IGN1dG9mZgogICAgZG11IDwtIGRtaXggJT4lIAogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIHNlbGVjdChhbnRpZ2VuZix0aW1lLGxvZ21maSxsb2dtZmlkaWZmKSAlPiUKICAgICAgZmlsdGVyKGFudGlnZW5mPT1hYiAmIHRpbWU9PSJBIiAmIGxvZ21maWRpZmY+aWN1dCkKICAgIAogICAgIyBpZiB0aGVyZSBhcmUgPDIgb2JzLCBjYW5ub3QgZXN0aW1hdGUgbWVhbiArIFNELCBzbyBza2lwCiAgICBpZihucm93KGRtdSk+PTIpIHsKICAgICAgCiAgICAgIGRtdSA8LSBkbXUgJT4lCiAgICAgICAgc3VtbWFyaXplKG11PW1lYW4obG9nbWZpKSxzZD1zZChsb2dtZmkpLHVjdXQ9bXUrMypzZCkKICAgIAogICAgICAjIGNsYXNzaWZ5IGFsbCBvYnNlcnZhdGlvbnMgdXNpbmcgdGhlIGRlcml2ZWQgY3V0b2ZmCiAgICAgIGRpIDwtIGRtaXggJT4lICAKICAgICAgZmlsdGVyKGFudGlnZW5mPT1hYikgJT4lCiAgICAgIG11dGF0ZShwb3NpPWZhY3RvcihpZmVsc2UobG9nbWZpPmRtdSR1Y3V0LDEsMCksbGV2ZWxzPWMoMCwxKSksCiAgICAgICAgICAgICBwb3NtaXg9ZmFjdG9yKHBvc21peCxsZXZlbHM9YygwLDEpKSkKICAgIAogICAgICB0YWJpIDwtIGFzLnZlY3Rvcih0YWJsZShkaSRwb3NtaXgsZGkkcG9zaSkpCiAgICAgIG5hbWVzKHRhYmkpIDwtIGMoIm1peDBjdXRpMCIsIm1peDFjdXRpMCIsIm1peDBjdXRpMSIsIm1peDFjdXRpMSIpCiAgICAgIGthcHBhaSA8LSBwc3ljaDo6Y29oZW4ua2FwcGEodGFibGUoZGkkcG9zbWl4LGRpJHBvc2kpKSRrYXBwYQogICAgICB0YWJpIDwtIGRhdGEuZnJhbWUodCh0YWJpKSkKICAgICAgdGFiaSA8LSB0YWJpICU+JQogICAgICAgIG11dGF0ZShOb2JzPW1peDBjdXRpMCttaXgxY3V0aTArbWl4MGN1dGkxK21peDFjdXRpMSwKICAgICAgICAgICAgIGFncmVlbWVudD0obWl4MGN1dGkwK21peDFjdXRpMSkvTm9icywKICAgICAgICAgICAgIGthcHBhPWthcHBhaSwKICAgICAgICAgICAgIGFudGlnZW5mPWFiLAogICAgICAgICAgICAgYWJjaGFuZ2U9aWN1dCwKICAgICAgICAgICAgIGFiY3V0b2ZmPWRtdSR1Y3V0KSAKICAgICAgCiAgICB9IGVsc2V7CiAgICAgIHRhYmkgPC0gZGF0YS5mcmFtZShtaXgwY3V0aTA9TkEsbWl4MWN1dGkwPU5BLG1peDBjdXRpMT1OQSxtaXgxY3V0aTE9TkEsTm9icz1OQSxhZ3JlZW1lbnQ9TkEsa2FwcGE9TkEsYW50aWdlbmY9YWIsYWJjaGFuZ2U9aWN1dCxhYmN1dG9mZj1OQSkKICAgICAgCiAgICAgIH0KCiAgICB9ICAKCmdyaWRtaXggPC0gZ3JpZG1peCAlPiUgbXV0YXRlKGFudGlnZW5mPWZhY3RvcihhbnRpZ2VuZixsZXZlbHM9bGV2ZWxzKGRtaXgkYW50aWdlbmYpKSkKCiMgaWRlbnRpZnkgdGhlIG9wdGltYWwgY2hhbmdlIGJhc2VkIG9uICUgYWdyZWVtZW50LCB0YWtlIG1pbmltdW0gY2hhbmdlCm9wdGNoZ21peCA8LSBncmlkbWl4ICU+JQogIGdyb3VwX2J5KGFudGlnZW5mKSAlPiUKICBtdXRhdGUobWF4YWdyPW1heChhZ3JlZW1lbnQsbmEucm09VFJVRSksCiAgICAgICAgIG1heGNoZz1pZmVsc2UoIGFicyhhZ3JlZW1lbnQtbWF4YWdyKTwwLjAwMSwxLDApKSAlPiUKICBmaWx0ZXIobWF4Y2hnPT0xKSAlPiUKICAjIG11dGF0ZShhZ3I5OT1pZmVsc2UoYWdyZWVtZW50PjAuOTkgJiAhaXMubmEoYWdyZWVtZW50KSwxLDApKSAlPiUKICAjIGZpbHRlcihhZ3I5OT09MSkgJT4lCiAgYXJyYW5nZShhbnRpZ2VuZixhYmNoYW5nZSkgJT4lCiAgZmlsdGVyKHJvd19udW1iZXIoKT09MSkgJT4lCiAgc2VsZWN0KGFudGlnZW5mLG9wdGNoZz1hYmNoYW5nZSxvcHRhZ3I9YWdyZWVtZW50LG9wdGthcD1rYXBwYSxvcHRjdXQ9YWJjdXRvZmYpCgpncmlkbWl4IDwtIGxlZnRfam9pbihncmlkbWl4LG9wdGNoZ21peCxieT0iYW50aWdlbmYiKSAlPiUKICBncm91cF9ieShhbnRpZ2VuZikKCgpgYGAKCiMjIyBGaWd1cmUgdG8gc3VtbWFyaXplIHJlc3VsdHMKClRoZSBib3R0b20gcGFuZWwgcGxvdHMgdGhlIGVzdGltYXRlZCBzZXJvcG9zaXRpdml0eSBjdXRvZmYgYXMgYSBmdW5jdGlvbiBvZiBjaGFuZ2UgaW4gTUZJIGxldmVscyB1c2VkIHRvIGlkZW50aWZ5IHByZXN1bWVkIHNlcm9jb252ZXJ0ZXJzLiBUaGUgbWVhbiBwbHVzIDMgU0RzIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgYW50aWJvZHkgbGV2ZWxzIGJlZm9yZSBwcmVzdW1lZCBzZXJvY29udmVyc2lvbiB3YXMgdXNlZCB0byBlc3RpbWF0ZSB0aGUgY3V0b2ZmLiBGb3IgYSBnaXZlbiBjdXRvZmYsIHRoZSBtaWRkbGUgYW5kIHRvcCBwYW5lbHMgc3VtbWFyaXplIGNsYXNzaWZpY2F0aW9uIGFncmVlbWVudCB3aXRoIHRoZSBtaXh0dXJlIG1vZGVsLWJhc2VkIHNlcm9wb3NpdGlpdHkgY3V0b2ZmIGJhc2VkIG9uIENvaGVuJ3MgS2FwcGEgKG1pZGRsZSkgYW5kIHByb3BvcnRpb24gYWdyZWVtZW50ICh0b3ApLgoKTGFiZWxlZCBkYXNoZWQgbGluZXMgaW5kaWNhdGUgdGhlIG1pbmltdW0gbWFnbml0dWRlIG9mIGNoYW5nZSBpbiBhbnRpYm9keSBsZXZlbHMgdGhhdCBsZWQgdG8gaGlnaGVzdCBhZ3JlZW1lbnQsIGFsb25nIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgc2Vyb3Bvc2l0aXZpdHkgY3V0b2ZmIHZhbHVlcywgQ29oZW4ncyBLYXBwYSwgYW5kIGFncmVlbWVudC4KCgpgYGB7ciBncmlkIG1peCBhZ3JlZW1lbnQgZmlnLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD05LCB3YXJuaW5nPUZBTFNFfQpwbG90X29wdGNoYW5nZShyZXN1bHRzPWdyaWRtaXgsY2xhc3NsYWI9Ik1peHR1cmUgbW9kZWwtYmFzZWQgY2xhc3NpZmljYXRpb24iKQpgYGAKCgoKIyBTZXNzaW9uIEluZm8KYGBge3Igc2Vzc2lvbiBpbmZvfQpzZXNzaW9uSW5mbygpCmBgYAoK